package com.linewell.encryption.services.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.linewell.encryption.pojo.SMInitInfo;
import com.linewell.encryption.services.SMEncryptionService;
import com.linewell.encryption.utils.Base64Utils;
import com.linewell.encryption.utils.ByteUtils;
import com.linewell.encryption.utils.sm2.SM2EnDeUtils;
import com.linewell.encryption.utils.sm2.SM2VerifySignUtils;
import com.linewell.encryption.utils.sm3.SM3Utils;
import com.linewell.encryption.utils.sm4.SM4Utils;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * 程序自行加解密 (本系统自行加解密)
 * Company: 南威软件股份有限公司
 *
 * @author wucan
 * @date 2019-03-14
 */
public class SystemSMEncryptionServiceImpl implements SMEncryptionService {

    /**
     * SM2的公钥
     * Base64
     */
    private String sm2PublicKey;
    /**
     * SM2的私钥
     * Base64
     */
    private String sm2PrivateKey;
    /**
     * SM4的秘钥
     */
    private String sm4Key;
    /**
     * iv偏移量密钥
     */
    private String sm4Iv;
    /**
     * SM2的公钥（SM2验证签名使用）
     * Base64
     */
    private String sm2SignPublicKey;
    /**
     * SM2的私钥（sm2签名使用）
     * Base64
     */
    private String sm2SignPrivateKey;

    public SystemSMEncryptionServiceImpl(SMInitInfo initInfo) {
        this.sm2PublicKey = initInfo.getSm2PublicKey();
        this.sm2PrivateKey = initInfo.getSm2PrivateKey();
        this.sm4Key = initInfo.getSm4Key();
        this.sm4Iv = initInfo.getSm4Iv();
        this.sm2SignPublicKey = initInfo.getSm2SignPublicKey();
        this.sm2SignPrivateKey = initInfo.getSm2SignPrivateKey();
    }

    @Override
    public String SM3(String secret) throws Exception {
        if (Objects.isNull(secret)) {
            throw new NullPointerException("SM3 secret is null");
        }
        return Hex.toHexString(SM3Utils.encrypt(secret));
    }

    @Override
    public String SM3ToBase64(String secret) throws Exception {
        if (Objects.isNull(secret)) {
            throw new NullPointerException("SM3 secret is null");
        }
        return Base64Utils.byteToBase64(SM3Utils.encrypt(secret));
    }

    @Override
    public String encodeSM2(Object secret) throws Exception {
        if (Objects.isNull(secret) || Objects.isNull(this.sm2PublicKey)) {
            throw new NullPointerException("encodeSM2 secret or key is null");
        }
        byte[] input;
        if (secret instanceof String) {
            input = secret.toString().getBytes(StandardCharsets.UTF_8);
        } else {
            input = JSONObject.toJSONString(secret, SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
        }
        String sign = SM2EnDeUtils.encrypt(Base64Utils.base64ToByte(this.sm2PublicKey), input);
        return Base64Utils.byteToBase64(ByteUtils.hexToByte(sign));
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T decodeSM2(String sign, Class<T> clazz) throws Exception {
        if (Objects.isNull(sign) || Objects.isNull(this.sm2PrivateKey)) {
            throw new NullPointerException("decodeSM2 sign or key is null");
        }
        byte[] input = Base64Utils.base64ToByte(sign);
        byte[] plainText = SM2EnDeUtils.decrypt(Base64Utils.base64ToByte(this.sm2PrivateKey), input);
        if (clazz.equals(String.class)) {
            return (T) new String(plainText);
        } else {
            return JSONObject.parseObject(plainText, clazz);
        }

    }

    @Override
    public String encodeSM4ECB(Object secret) throws Exception {
        if (Objects.isNull(secret) || Objects.isNull(this.sm4Key)) {
            throw new NullPointerException("SM4ECB secret or key is null");
        }
        byte[] input;
        if (secret instanceof String) {
            input = secret.toString().getBytes(StandardCharsets.UTF_8);
        } else {
            input = JSONObject.toJSONString(secret, SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
        }

        Cipher cipher = SM4Utils.generateEcbCipher("SM4/ECB/PKCS5Padding", Cipher.ENCRYPT_MODE, ByteUtils.hexToByte(this.sm4Key));
        byte[] sign = cipher.doFinal(input);
        return Base64Utils.byteToBase64(sign);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T decodeSM4ECB(String sign, Class<T> clazz) throws Exception {
        if (Objects.isNull(sign) || Objects.isNull(this.sm4Key)) {
            throw new NullPointerException("decodeSM4ECB sign or key is null");
        }
        byte[] input = Base64Utils.base64ToByte(sign);
        Cipher cipher = SM4Utils.generateEcbCipher("SM4/ECB/PKCS5Padding", Cipher.DECRYPT_MODE, ByteUtils.hexToByte(this.sm4Key));
        byte[] plainText = cipher.doFinal(input);
        if (clazz.equals(String.class)) {
            return (T) new String(plainText);
        } else {
            return JSONObject.parseObject(plainText, clazz);
        }
    }

    @Override
    public String encodeSM4CBCToIv(Object secret) throws Exception {
        if (Objects.isNull(secret) || Objects.isNull(this.sm4Key) || Objects.isNull(this.sm4Iv)) {
            throw new NullPointerException("SM4CBCToIv secret or key is null");
        }
        byte[] input;
        if (secret instanceof String) {
            input = secret.toString().getBytes(StandardCharsets.UTF_8);
        } else {
            input = JSONObject.toJSONString(secret, SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
        }
        Cipher cipher = SM4Utils.generateCbcCipher("SM4/CBC/PKCS5Padding", Cipher.ENCRYPT_MODE, ByteUtils.hexToByte(this.sm4Key), ByteUtils.hexToByte(this.sm4Iv));
        byte[] sign = cipher.doFinal(input);
        return Base64Utils.byteToBase64(sign);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T decodeSM4CBCToIv(String sign, Class<T> clazz) throws Exception {
        if (Objects.isNull(sign) || Objects.isNull(this.sm4Key) || Objects.isNull(this.sm4Iv)) {
            throw new NullPointerException("decodeSM4CBCToIv sign or key is null");
        }
        byte[] input = Base64Utils.base64ToByte(sign);
        Cipher cipher = SM4Utils.generateCbcCipher("SM4/CBC/PKCS5Padding", Cipher.DECRYPT_MODE, ByteUtils.hexToByte(this.sm4Key), ByteUtils.hexToByte(this.sm4Iv));
        byte[] plainText = cipher.doFinal(input);
        if (clazz.equals(String.class)) {
            return (T) new String(plainText);
        } else {
            return JSONObject.parseObject(plainText, clazz);
        }
    }

    @Override
    public String signSM2(Object secret) throws Exception {
        if (Objects.isNull(secret) || Objects.isNull(this.sm2SignPrivateKey)) {
            throw new NullPointerException("system signSM2 parameter is null");
        }
        byte[] input;
        if (secret instanceof String) {
            input = secret.toString().getBytes(StandardCharsets.UTF_8);
        } else {
            input = JSONObject.toJSONString(secret, SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
        }
        return SM2VerifySignUtils.sign2SM2(Base64Utils.base64ToByte(this.sm2SignPrivateKey), input);
    }

    @Override
    public Boolean verifySignSM2(Object secret, String sign) {
        if (Objects.isNull(secret) || Objects.isNull(sign) || Objects.isNull(this.sm2SignPublicKey)) {
            throw new NullPointerException("system verifySignSM2 parameter is null");
        }
        byte[] input;
        if (secret instanceof String) {
            input = secret.toString().getBytes(StandardCharsets.UTF_8);
        } else {
            input = JSONObject.toJSONString(secret, SerializerFeature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
        }
        return SM2VerifySignUtils.verifySignSM2(Base64Utils.base64ToByte(this.sm2SignPublicKey), input, Base64Utils.base64ToByte(sign));
    }


}
